package mote;

import java.lang.String;
import java.lang.StringBuilder;
import net.tinyos.message.*;
import java.io.IOException;

import serialPrintf.PrintfClient;
import serialPrintf.PrintfMsg;
import net.tinyos.packet.*;
import net.tinyos.util.*;

/**
 * MoteBridge connects a mote attached to the serial port to the MoteCollection.
 * @author Khalid Alhokail, Vineet Gupta, Oren Melzer, David Schneider
 *
 */
public class MoteBridge extends PrintfClient implements MoteInterface {
	public static final int TELOSB = 115200;
	public static final String DEFAULT_PORT = "/dev/ttyUSB0";
	
	private MoteCollection motes;
	private String device;
	private int baudrate;
	private MoteIF mif;
	
	/**
	 * Creates a new instance of MoteBridge with the given settings
	 * @param collection	The collection of motes to interface with
	 * @param device		The device where the mote hub is connected. For example "/dev/ttyUSB0".
	 * @param baudrate		The baudrate for communication with the mote. For example 115200 for a telosb mote.
	 */
	public MoteBridge(MoteCollection collection, String device, int baudrate){
		motes = collection;
		this.device = device;
		this.baudrate = baudrate;
		setupSerial();
	}
	
	/**
	 * Creates a new instance of MoteBridge with the given settings. 
	 * Uses Telosb baudrate as default baudrate.
	 * @param collection	The collection of motes to interface with
	 * @param device		The device where the mote hub is connected. For example "/dev/ttyUSB0".
	 */
	public MoteBridge(MoteCollection collection, String device){
		this(collection, device, TELOSB);
	}
	
	/**
	 * Creates a new instance of MoteBridge with the given settings. 
	 * Uses "/dev/ttyUSB0" as the default port.
	 * @param collection	The collection of motes to interface with
	 * @param baudrate		The baudrate for communication with the mote. For example 115200 for a telosb mote.
	 */
	public MoteBridge(MoteCollection collection, int baudrate){
		this(collection, DEFAULT_PORT, baudrate);
	}
	
	/**
	 * Creates a new instance of MoteBridge with the given settings. 
	 * Uses Telosb baudrate as default baudrate. Uses "/dev/ttyUSB0" 
	 * as the default port.
	 * @param collection	The collection of motes to interface with
	 */
	public MoteBridge(MoteCollection collection){
		this(collection, DEFAULT_PORT, TELOSB);		
	}
	
	
	/** 
	 * Begins execution of the MoteBridge, should be called on initialization.
	 * 
	 */
	private void setupSerial(){
		String source = "serial@"+device+":"+baudrate;
		try {
			PhoenixSource phoenix;
			if (source == null) {
				phoenix = BuildSource.makePhoenix(PrintStreamMessenger.err);
			}
			else {
				phoenix = BuildSource.makePhoenix(source, PrintStreamMessenger.err);
			}
			System.out.print(phoenix);
			
			//This line terminates the program if there is not a valid device
			mif = new MoteIF(phoenix);
			setMoteIF(mif);	
		}catch (Exception e){
			System.out.println("Error: could not connect to hub mote.\nPlease verify it is connected.\nIf currently connected and still not detecting, disconnect and reconnect the mote.");
		}
		
	}
 
	/**
	 * Overrides the messageRecieved function from PrintfClient. Essentially is
	 * an identical method except it sends the result to processString instead of
	 * the output. This method should never be called by uses of the API.
	 * @param to		The destination that the message was to
	 * @param message	A PrintfMsg that was passed by the serial reader
	 */
	public void messageReceived(int to, Message message){
		PrintfMsg msg = (PrintfMsg)message;
		StringBuilder result = new StringBuilder();
		for(int i=0; i<PrintfMsg.totalSize_buffer(); i++){
			char nextChar = (char)(msg.getElement_buffer(i));
			if(nextChar != 0)
				result.append(nextChar);
		}
		//System.out.println("Recieved: "+result);
		processString(result.toString());
	}
	
	/**
	 * Sends a Packet through the serial port
	 * @param packet	The packet to be sent
	 * @return If packet is send successfully
	 */
	private boolean writeSerial(NetworkPacket packet){
		Message msg = new Message(packet.tinyOSNetworkPacket());
		try{
			mif.send(NetworkPacket.AM_HUB_ID, msg);
			return true;
		}catch (IOException e){
			System.err.println("Error writing to serial");
			return false;
		}
	}

	/**
	 * Processes a string of input from the serial port and adjust values
	 * of motes as necessary.
	 * @param input	The incoming String to process
	 */
	private void processString(String input){
		
		NetworkPacket packet = new NetworkPacket(input);
		//System.out.println("Packet parsed: "+packet);
		
		if(packet.type != NetworkPacket.BAD_PACKET){
			//Add the mote if the mote does not already exist
			motes.addMote(packet.source);
		}
		
		switch (packet.type) {
			case NetworkPacket.LIGHT_SENSOR:
				motes.changeReading(packet.data, packet.source);
				break;
			default:
				;
		}
	}
	
	/**
	 * Sets the LEDs of the given mote
	 * @param moteId	The mote to set the LEDs
	 * @param values	A byte that represents the LED values
	 * <br>	1 for LED 0.
	 * <br>	2 for LED 1.
	 * <br> 4 for LED 2.
	 * <br>For example, 7 is all on, and 0 is all off. 
	 * @return		If the LEDs appear to be successfully set.
	 */
	public boolean setLEDs(int moteId, byte values){
		return writeSerial(new NetworkPacket(NetworkPacket.LED_COMMAND, moteId, NetworkPacket.AM_COMPUTER_ID, values));
	}
	
	/**
	 * Sets the sampling interval of the motes. This will do nothing in the 
	 * mote simulator.
	 * @param twentiethsOfASecond	How many twentieths of a second to wait 
	 * 								between transmitting the light readings. 
	 * @return		If the sampling interval appears to have been changed.
	 */
	public boolean setSamplingInterval(int moteId, int twentiethsOfASecond){
		return writeSerial(new NetworkPacket(NetworkPacket.SET_SAMPLING_INTERVAL, moteId, NetworkPacket.AM_COMPUTER_ID, twentiethsOfASecond));
	}
}
